home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / C / Frameworks / Hsoi's App Shell 1.0a4 / Hsoi's App Shell Source / HASUtilCursors.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-01-28  |  26.6 KB  |  679 lines  |  [TEXT/CWIE]

  1. /*
  2.     HASUtilCursors.c from Hsoi's App Shell. © 1995-1997 John C. Daub.  All rights reserved.
  3.     
  4.     This file contains various cursor related functions and utilities.
  5. */
  6.  
  7. #pragma mark ••• #includes •••
  8.  
  9. #ifndef _WASTE_
  10. #include "WASTE.h"
  11. #endif
  12. #include "HASGlobals.h"
  13. #ifndef __HSOIS_APP_SHELL__
  14. #include "HASMain.h"
  15. #endif
  16. #include "HASUtilCursors.h"
  17. #include "HASUtilDialogs.h"
  18. #include "HASUtilities.h"
  19. #include "HASSoundSpeech.h"
  20. #ifndef __QUICKDRAW__
  21. #include <QuickDraw.h>
  22. #endif
  23. #ifndef __RETRACE__
  24. #include <Retrace.h>
  25. #endif
  26. #ifndef __CURSORCTL__
  27. #include <CursorCtl.h>
  28. #endif
  29.  
  30.  
  31. #pragma mark -
  32. #pragma mark ••• Init Cursors •••
  33.  
  34. // HsoiSetUpCursors() is called at startup time to get our various cursors set up.
  35. // i like obtaining Handles to frequently used cursors now and using these globals
  36. // in SetCursor() instead of calling GetCursor() all the time (avoid accessing the
  37. // toolbox/rom's to save a little time).
  38.  
  39. void    HsoiSetUpCursors( void )
  40. {
  41.     // get our i-beam and static (not animated) watch cursors
  42.     
  43.     gEditCursor = GetCursor(iBeamCursor);
  44.     gWaitCursor = GetCursor(watchCursor);
  45.  
  46.     // move and lock them high in the application heap
  47.     
  48.     HLockHi( (Handle)gEditCursor );
  49.     HLockHi( (Handle)gWaitCursor );
  50.     
  51.     return;
  52. }
  53.  
  54.  
  55. #pragma mark -
  56. #pragma mark ••• Manual Spin Cursor •••
  57.  
  58. OSErr    HsoiStartManualSpinning( void )
  59. {
  60.     OSErr        err = noErr;
  61.     
  62.     // now get our animated wait cursor
  63.     
  64.     // get a handle to the 'acur' resource..
  65.     
  66.     gAnimWatchCursor = (acurHandle)GetResource( TYPE_ANIMATED_CURSOR, rSpinningWatchACUR );
  67.     
  68.     if ( gAnimWatchCursor == nil ) // didn't get it, probably not there or not enough memory to load it
  69.         HsoiDoError( rErrorStrings, errFailGetResource, ResError(), kErrDeath ); // death, i know..extreme
  70.         
  71.     // move it high in the heap so we don't fragment our memory
  72.  
  73.     MoveHHi( (Handle)gAnimWatchCursor );
  74.     err = ResError();
  75.     if ( err != noErr )
  76.     {
  77.         // if the handle is locked (i.e. we can't move it)
  78.         
  79.         if ( err == memLockedErr )
  80.         {
  81.             HUnlock( (Handle)gAnimWatchCursor );
  82.             MoveHHi( (Handle)gAnimWatchCursor );
  83.             HLock( (Handle)gAnimWatchCursor );
  84.             err = noErr;
  85.         }
  86.         else
  87.         {
  88.             HsoiDoError( rErrorStrings, errFailGetResource, err, kErrDeath );
  89.         }
  90.     }
  91.         
  92.     // initialize the rotation routines (glue for these, in CW, is in the
  93.     // ToolLibs.o).  (right now, this isn't a VBL task...i.e. if you want the
  94.     // cursor you spin, you'll have to do it all yourself.  see my sample
  95.     // movable modal dialog (the progress bar thing) for an example of this)
  96.     
  97.     // or, see later on in this file for how to do a VBL spinning cursor (i.e.
  98.     // you don't have to do it all yourself!)
  99.     
  100.     InitCursorCtl( gAnimWatchCursor );    
  101.  
  102.     SpinCursor( 0 );    // get our cursor ready to spin!
  103.  
  104.     return err;
  105. }
  106.  
  107. OSErr    HsoiStopManualSpinning( void )
  108. {
  109.     OSErr    err = noErr;
  110.  
  111.     // unlock the animated cursor resources
  112.     
  113.     HUnlock( (Handle)gAnimWatchCursor );
  114.     
  115.     // make sure it can be purged
  116.     
  117.     HPurge( (Handle)gAnimWatchCursor );
  118.     
  119.     // and dispose of it
  120.     
  121.     HsoiForgetResource( (Handle *)&gAnimWatchCursor );
  122.     
  123.     // set the cursor to the arrow
  124.     
  125.     InitCursor();
  126.         
  127.     
  128.     return err;
  129. }
  130.  
  131. #pragma mark -
  132. #pragma mark ••• Other Cursor Utils •••
  133.  
  134. // HsoiIBeamIt() would be called in a dialog filter so that it's called "constantly" to
  135. // track the movement of the cursor.  If the cursor moves over an active edit line, the
  136. // cursor is changed to an I-Beam cursor, else an arrow.
  137.  
  138. // this function is not necessary for HAS since we're under System 7 (at least), cause
  139. // system 7's dialog manager provides a routine (SetDialogTracksCursor()) to do this for us.
  140. // but, i'll leave the code in here just in case you want to know about this sort of thing
  141. // (cause tracking a cursor is something we all need to know how to do)
  142.  
  143. void    HsoiIBeamIt(WindowRef window)
  144. {
  145.     Point    mouseAt;
  146.     Rect    borderRect;
  147.     short    itemNum;
  148.     
  149.     // first get the current edit line out of the dialog record
  150.     
  151.     itemNum = GetDialogKeyboardFocusItem(window);
  152.     HsoiGetDialogItemRect( window, itemNum, &borderRect );
  153.     
  154.     // find where the mouse is at
  155.     
  156.     GetMouse(&mouseAt);
  157.     
  158.     // if it's within our dialog item, change to the i-beam else the arrow
  159.     
  160.     if (PtInRect(mouseAt, &borderRect))
  161.     {
  162.         SetCursor( *gEditCursor );
  163.  
  164.         // or, you could call it like this:
  165.         // SetCursor(*(GetCursor(1)));
  166.         // if you don't want dependance on the HAS globals
  167.         
  168.     }
  169.     else
  170.     {
  171.         // This means I'm callng InitCursor every time through my filter
  172.         // this is a little excessive (ha!) but doesn't hurt anything.
  173.         // You could set a global or something if this really offends you
  174.     
  175.         InitCursor();
  176.         
  177.     }
  178.     
  179.     return;
  180.  
  181.  
  182. // HsoiAdjustCursor() is called in the main event loop.  it finds where the
  183. // cursor is and then adjusts it's appearance based upon the mouse's location
  184. // on the screen
  185.  
  186. void    HsoiAdjustCursor( Point mouseLoc, RgnHandle mouseRgn )
  187. {
  188.     WindowRef    window;
  189.     
  190.     /*    by default, set mouseRgn to the whole QuickDraw corrdinate plane, */
  191.     /*    so that we never get mouse moved events */
  192.     
  193.     SetRectRgn( mouseRgn, -MAXSHORT, -MAXSHORT, MAXSHORT, MAXSHORT );
  194.  
  195. /*
  196.     the cursor is set to the watch cursor by DoMovableModalDialog(), and removed (set back
  197.     to the arrow or whatever) when DialogHit() is called or the movable modal dialog
  198.     is dismissed.  Futhermore, it is necessary to make sure that WEAdjustCursor() does
  199.     check if gPeriodicTask, cause otherwise it'll change it! 
  200. */    
  201.     if ( gPeriodicTask )
  202.         return;    
  203.  
  204.     /*    give text services a change to set the cursor shape */
  205.     
  206.     if ( gHasTextServices )
  207.     {
  208.         if ( SetTSMCursor( mouseLoc ) )
  209.             return;
  210.     }
  211.     
  212.     /*
  213.         if there is a window open, give WEAdjustCursor() an opportunity to set the cursor.
  214.         WEAdjustCursor() insersects mouseRgn (if supplied) with a region within which
  215.         the cursor is to regain its shape.  if the cursor is outside the view region,
  216.         this is subtracted from mouseRgn.
  217.         
  218.         and note, only call WEAdjustCursor if there is a window, the window is a
  219.         document window (at least needs a WASTE instance attached to it, but we
  220.         don't want the clipboard window to get an edit cursor else that might "tell" the
  221.         user they can edit the clipboard window), and there isn't a periodic task in
  222.         effect (but this is already taken care of above).
  223.     */
  224.     
  225.     window = FrontWindow();
  226.  
  227.     if ( (window != nil) && HsoiIsDocumentWindow( window ) )
  228.     {
  229.         if ( gHiliting )
  230.         {
  231.             InitCursor();
  232.             return;
  233.         }
  234.         
  235.         if ( WEAdjustCursor( mouseLoc, mouseRgn, HsoiGetWindowWE(window) ) )
  236.             return;
  237.     }
  238.  
  239.  
  240.     /*    if we made it this far, set the cursor to the arrow cursor */
  241.  
  242.     // don't use InitCursor() cause that'll make the cursor flicker cause InitCursor()
  243.     // shows the cursor (i think it calls ShowCursor()).  using SetCursor will set
  244.     // it properly to an arrow, but if it's hidden, it will remain hidden.
  245.         
  246.     SetCursor( &qd.arrow );
  247.     
  248.     return;
  249. }
  250.  
  251.  
  252. #pragma mark -
  253. #pragma mark ••• VBL Spinning Cursor •••
  254.  
  255. /*
  256.     what follows here is code related to spinning a cursor.  but this isn't any old
  257.     spin cursor stuff...it's spinning a cursor as a VBL Task!  Just what is a VBL
  258.     Task?  well first, VBL stands for Vertical Blanking Interrupt.  This is something
  259.     generated by the Vertical Retrace Manager, "the part of the Operating System
  260.     that schedules and executes recurrent tasks during vertical retrace interrupts.
  261.     You can use the Vertical Retrace Manager to execute simple, repetitve tasks and
  262.     avoid having to execute those tasks repeatedly in your application's main event
  263.     loop" (Inside Macintosh: Processes, 4-3).
  264.     
  265.     Here's the jist of how it works. (this is taken from Inside Macintosh: Processes,
  266.     4-4).
  267.     
  268.     The video circuitry in a Macintosh computer, whether built-in or external, refreshes
  269.     the screen at regular intervals.  For built-in monitors, the screen is refreshed
  270.     approximately 60 times per second; for external monitors, the screen is refreshed at
  271.     intervals determined by the associated video hardware.  To refresh the screen, the
  272.     monitor's electron beam draws one pixel at a time, starting at the upper-left corner
  273.     of the screen and moving quickly to the lower-right corner.  When the electron beam
  274.     returns from the lower-right corner of the screen to the upper-left corner, the video
  275.     circuitry generates a "vertical retrace interrupt" or "vertical blanking (VBL) interrupt".
  276.     
  277.     The Vertical Retrace Manager is the part of the Operating System that schedules and
  278.     executes tasks -- known as VBL tasks -- during a vertical retrace interrupt.  The
  279.     Operating System itself uses the Vertical Retrace Manager to perform some important
  280.     housekeeping operations, such as moving the cursor in response to mouse
  281.     movements and checking whether the current application's stack has expanded into it's
  282.     heap.  Within the limitations described in this chapter [Chapter 4 of IM:Processes],
  283.     you can use the VRMgr to install your own recurrent tasks [like the spinning cursor
  284.     we're about to do].
  285.     
  286.     In general, the VRMgr is useful for small, repetitive tasks that do not allocate or
  287.     release memory and that you do not want to execute in your main event loop.  Whenever
  288.     possible, it is best to manage periodic tasks within your main event loop [for
  289.     example, the sample movable modal dialog coupled with HsoiDoMainLoopTasks].  However,
  290.     if you want some task some task to execute repetitvely at a time when you do not want
  291.     to reenter your main event loop (perhaps because you don't want your application to
  292.     be switched out during some lengthy operation), it might be possible to use the VRMgr
  293.     to execute the task.
  294.     
  295.     The principal limitation on VBL tasks (aside from the limitations on any interrupt-time
  296.     processing) is that they cannot execute more frequently than once per VBL interrupt.
  297.     [the exact timing of a VBL interrupt will vary from monitor to monitor based upon
  298.     that monitor's refresh frequency.  If you need more exact timing and/or more often
  299.     execution than a VBL task would allow, use the Time Manager, explained in IM: Processes.
  300.     Additionally, the VRMgr is NOT an absolute timing mechanism...it's always relative
  301.     to VBL interrupts.  If absolute time delays are important, use the Time Manager.
  302.     VRMgr stuff is good in cases where the scheduled actions need simply to be synchronized
  303.     with other VBL tasks, such as moving the cursor or refreshing the screen].
  304.     
  305.     I don't want to include all of Chapter 4 of IM:Processes here (you can get this
  306.     at your local bookstore, or even off the Internet from Apple's ftp and web
  307.     sites...try http://dev.info.apple.com/).  But this should be sufficient to explain
  308.     just what the heck a VBL task is and how it works so you can understand what i'm
  309.     about to do.  And just as an fyi sorta thing, here are the VBL tasks installed
  310.     by the MacOS (from IM: Processes, 4-5):
  311.     
  312.         Every interrupt:
  313.             • update the value of the global variable "Ticks" which a program may access
  314.               throughout the routine TickCount()
  315.             • call the "stack sniffer" to see if the current application's stack and heap
  316.               have collided.  If so, the task calls the System Error Handler
  317.             • update the position of the cursor
  318.         
  319.         Every 30 interrupts
  320.             • Check whether the user has inserted a disk or a mounted a volume.  If so,
  321.               the task posts a disk-inserted event.
  322.         
  323.         Evert 32 interrupts
  324.             • Check whether a keyboard has been reattached after having been detached. If
  325.               so, the task resets the keyboard.
  326.     
  327.     incidentally, some VBL routines may execute only on certain computers or only in
  328.     certain versions of system software.  Also, VBL tasks installed by the OS are not
  329.     maintained in the same queue used for application-defined VBL tasks.
  330.     
  331.     So, that should be more than enough for you to start understanding VBL tasks and
  332.     this spinning cursor stuff I'm about to do.  If you'd like to read more about
  333.     this sort of thing  (and you should!), check out Inside Macintosh.  I do highly
  334.     recommend that you do read IM:Processes (all of it, it's pretty short) if you
  335.     want to do anything with VBL tasks...it's a pretty easy thing to use, but there
  336.     are lots of things to be careful about.  it's too much stuff to list here, so
  337.     getting your own copy of the book is best.
  338. */
  339.  
  340. /*
  341.     Now, some other notes :)
  342.     
  343.     There are all sorts of ways to spin the cursor.  I impliment a nice way of doing
  344.     this with my movable modal dialog progress bar thing (from the Dialogs menu,
  345.     select MovableModal Dialog).
  346.     
  347.     I've also seen lots of sample code that can do VBL tasks.  For example, in Scott
  348.     Knaster's book "Macintosh Programming Secrets" 2nd ed. Chapter 10, Knaster gives
  349.     us a wonderful VBL spinning cursor.  But there is a problem with Knaster's code.
  350.     It uses assembly language!  Now, this really isn't a problem, in fact it's
  351.     pretty cool.  However, I would prefer to not use any assembly in HAS.  why?
  352.     well, how many people out there know assembly? (if you don't, you should at least
  353.     get familiar with it for debugging and optimization purposes...but that's an
  354.     advanced thing...and fyi, 2 good books for learning this sort of thing, tho
  355.     a bit old and out of date, are the MacsBug Reference and Debugging Guide
  356.     (from Apple Computer.  as of this writing, it covers MacsBug 6.2 tho the latest
  357.     version of Macsbug is 6.5.2, so a little out of date, but still relevant) and
  358.     Scott Knaster's How to Write Macintosh Software (currently in it's 3rd edition
  359.     and desperately, IMHO, in need up and update since it's all in Pascal and
  360.     doesn't cover PowerMacs and assembly for PowerPC chips, only assembly
  361.     for 680x0 chips)).
  362.     
  363.     Also, the code in Knaster's MPS book is made for THINK C (not CodeWarrior).
  364.     so the changes would be problematic (actually, I'm just lazy).
  365.     
  366.     And also, this assembly probably won't work on PowerMacs! (dunno, haven't
  367.     tried it, but I remember something clicked in my mind knowing that the code
  368.     won't work...I think it's cause of the A5 register stuff and storing stuff
  369.     at A0).
  370.     
  371.     So, what I've got here is basically taken right out of IM:Processes (with
  372.     some appropriate and necessary modifications), and perhaps a bit of
  373.     Knaster-knowledge thrown in.
  374.     
  375.     Incidentally, tho Knaster's code probably can't work in this sort of situation
  376.     I'm going for, his explanations and stuff is still worth a million bucks.
  377.     I still say that if you have the money, get Knaster's book(s).
  378.     
  379.     OH!...one last thing.  right now I'm only implimenting this spinning cursor
  380.     in CreateWindow() when/if there is a file to read in. To test all this stuff,
  381.     i created a HUGE text file (i saved my Help stuff as a text file, then select
  382.     all, copy, paste...then select all again, copy paste...etc...did it over and
  383.     over...the file is about 842k (text, styl, soup, etc) all in all..takes some
  384.     time to open up needless to say!  Things seem to work ok.)
  385.     
  386.     Now, while i was creating this file, with all the pasting i was doing (and
  387.     as i progressed, the pastes got bigger and bigger) it was taking some substantial
  388.     time (at least substantial on my IIvx with it's 68030 @ 32mHz) to paste!
  389.     so, if you'd like to in your program (or your toying with HAS) you might want
  390.     to use this VBL code anytime you would need a wait cursor (pasting is definately
  391.     one place, there could be others...you decide).  One nice thing about this code
  392.     is that the spinning cursor isn't activated immediately...a couple seconds go
  393.     by before the spinning cursor happens.  this way, if the operation you're performing
  394.     takes a very short time (like less than a couple seconds) the "wait" cursor
  395.     never kicks in (tho the VBL task was installed).  then if the operation was
  396.     taking a long time, the spinning wait cursor happens...joys of VBL eh?  you'd
  397.     not really be able to determine such a thing normally...like i could determine
  398.     all this "manually" with something like my HsoiDoMainLoopTasks, but with
  399.     something like pasting (calling WEPaste), you'd have to patch WEPaste, that's
  400.     not recommended to do (else risk non-conformity with the WASTE API and you'd
  401.     have to "reinstall" your custom hacks to WASTE every time a new WASTE API was
  402.     released).
  403.     
  404.     so, this is a cool thing eh?
  405.     
  406.     but one thing to be wary about...if you had a spinning cursor then switched
  407.     your app into the background, you could have problems...(haven't tried this
  408.     myself yet, but i'm sure it'd be a problem).  be careful about this...
  409.     layer switching (i.e. suspend/resume events) with an application-specific-active
  410.     VBL task installed could leave the VBL task running and make for a confused
  411.     user.  right now we get around this by calling Start and Stop around and
  412.     in functions where we know a layer switch could never occur.  In this sort
  413.     of instance, i can think of 2 possible things to do:  1. create something
  414.     similar to the HsoiDoMainLoopTasks and my sample movable modal dialog.
  415.     that coupled with the rest of the event loop (and the rest of HAS's structuring)
  416.     makes things work ok.  2.  you could do something in your suspend/resume
  417.     event handling to check for your application's VBL tasks and remove/reinstall
  418.     them at suspend/resume time.  (actually, as long as you call these VBL
  419.     tasks around/in a function that doesn't bother with the main event loop (like
  420.     how i'm using it solely in HsoiCreateWindow), you're ok...if you want to
  421.     learn more about this sort of thing, IM:Processes covers all the details
  422.     you'd need to know about VBL's and application execution with major/minor
  423.     context switches, etc...again, another good reason to get IM:Processes)
  424.     
  425.     also, one thing I'm curious about....there are 2 general types of VBL tasks.
  426.     a system-based VBL task (where the OS maintains just one task queue and processes
  427.     the tasks in that queue when it receives a VBL interrupt and is not linked to
  428.     any external device, like a monitor).  This is what I am currently doing.
  429.     
  430.     however, one thing I've tried to do with HAS is demonstrate how to have code
  431.     that's happy in multiple monitor setups (like HsoiDoDrag()) since so few people
  432.     know how to do this and so few apps are happy with multiple monitors (something
  433.     you should strive for: an app happy regardless (pretty much) of the user's
  434.     computer setup).  There is another VBL task type: a slot-based VBL task.
  435.     this type of task is linked to an external video monitor.  because different
  436.     monitors can have different refresh rates and hence might execute VBL tasks
  437.     at different times, the VRMgr maintains a separate task queue for each video
  438.     device attached to the computer.  then when a vbl interrupt occurs for a
  439.     particular device, the VRMgr executes any tasks in the queue for the slot holding
  440.     that monitor's video card. (and you'd then install a slot-based VBL task via
  441.     SlotVInstall()).
  442.     
  443.     so, how might this code behave (and break?) in multiple monitor setups?  if
  444.     the cursor got moved onto another monitor while the task was executing, i'm
  445.     certain the redraws (remember, cool thing with VBL tasks is that the animation
  446.     would be synced with the screen refresh for nice animation) would fall
  447.     differently (different monitors, different refresh rates) and cause funky
  448.     drawing of the cursor...i hypothesize that the VBL task would still occur
  449.     (just based upon the system VBL queue), but just (re)draw all funky, y'know?
  450.     
  451.     but then, it's hard for me to test and figure this out since i only have
  452.     a single monitor at home :(
  453.     
  454.     I'd think what you'd want to do would be in the Start function, instead
  455.     of just calling VInstall, you'd step through the device list and for
  456.     each device call SlotVInstall...then i'd figure if they moved the cursor
  457.     to another monitor, it'd work ok...and in the Stop function, do the
  458.     same thing but call SlotVRemove.  but again, this is all just my guess...
  459.     
  460.     and finally (i'm almost done, i hope), i hope that all my VBL code and
  461.     usage falls within the parameters of using a VBL task (these rules are
  462.     set forth in IM:Processes 4-6).  here's hoping...but heck, this code
  463.     is pretty much taken from Inside Macintosh..you'd think Apple wouldn't
  464.     write bad code, y'know?
  465.  
  466.     So, I guess the bottom line of it all is, pray what i have works (sure using
  467.     VInstall works, might flicker a bit on the redraws, but spinning a cursor
  468.     doesn't require graphics precision like a game might), send me money to
  469.     let me buy another monitor and a decent computer to use 2 monitors on! (just
  470.     kidding!), and YOU get a copy of IM:Processes and read up!
  471.     
  472.     Last thing:  i forgot just who helped me with this code!  the code as written
  473.     in IM:Processes did involve some assembly and wouldn't work on PowerMacs.
  474.     I posted on comp.sys.mac.programmer.help and 2 guys replied to me (the joys
  475.     of usenet news!).  but i forgot the guys's names and email addresses!  argh!
  476.     
  477.     well, one guy i know is named Skip...and i know i have their names around
  478.     here somewhere....when i find them, i'll be sure to include them!  the
  479.     biggest thing they helped me with was modifying the code to work with PowerMacs
  480.     and 68k Macs all from the same source.  one of them had the right idea 
  481.     (the #pragma parameter) stuff, but it wasn't quite right, so i just had to do
  482.     a little research and modification and viola!  thanx guys!
  483.     
  484.     Now, enough talk, let's code!
  485. */
  486.  
  487.  
  488. // interesting to note, we're not going to use our 'acur' resource.  we probably could,
  489. // but it's not worth the hassle
  490.  
  491. // a little global for easy data access
  492.  
  493. CursorTask gMyCursTask;
  494.  
  495. // and a global UPP for our callback
  496.  
  497. VBLUPP    gMyVBLUPP = nil;
  498.  
  499.  
  500. // HsoiChangeCursor is the nice little function the VRMgr actually calls to "animate"
  501. // the cursor.  This is what's installed into the VBLTask
  502.  
  503. /*
  504.     Ok...fun note...
  505.     
  506.     I originally did this a bit differently, using conditonal macros to get
  507.     different function signatures and prototypes.  Now in some places, this might
  508.     work ok, but here, it doesn't work.  Due to the nature of the VRMgr, 68k/PPC
  509.     chips, and the MacOS, we have to actually perform things a bit strangely to
  510.     get the same code to work on either chip.
  511.     
  512.     it worked fine on the 68k side of things...but when i tried it on a PowerMac,
  513.     it died everywhere....  I got a bomb saying "Math Coprocessor not installed"
  514.     and had to restart...
  515.     
  516.     now, this shouldn't even be an issue on the PowerMacs anyways, but oh well.
  517.     there's a tech note somewhere explaining this strange error, but i won't sweat
  518.     it now...
  519.     
  520.     point is, i took Skip's idea of using the #pragma parameter method.  his
  521.     code wasn't correct (it was off the top of his head), but that coupled with
  522.     some info i got from the CW8 C/C++/Assembly Language Ref (QuickView database,
  523.     just did a search through all the db's using "pragma" as the keyword) and found
  524.     the info i needed.  That gave me the proper syntax for this all.
  525.     
  526.     I have just tested it on a 68k and this method worked great!  and i tried it
  527.     on a PowerMac and it worked fine too.  So, a nice way to get VBL's working regardless
  528.     of the chip!
  529. */
  530.  
  531. // the little magic that makes it all work.... :)
  532.  
  533. #if !GENERATINGCFM
  534.     #pragma parameter HsoiChangeCursor( __A0 )
  535. #endif
  536.  
  537. // basically, if making a 68k app....
  538.  
  539. #if !GENERATINGCFM
  540. void HsoiChangeCursor( CursorTaskPtr recPtr );        // function prototype
  541.  
  542. void HsoiChangeCursor( CursorTaskPtr recPtr:__A0 )    // beginning of the actual function
  543. #else // PPC or CFM app
  544. void HsoiChangeCursor( CursorTaskPtr recPtr );        // function prototype
  545. void HsoiChangeCursor( CursorTaskPtr recPtr )        // beginning of actual function
  546. #endif
  547.  
  548. // note how the conditional preprocessor macros work above to give us the right
  549. // prototype and function name/arguments for this following stuff....
  550.  
  551. {
  552.     // if the cursor is busy, we should not change it
  553.     
  554.     if ( !LMGetCrsrBusy() )
  555.     {
  556.         // update the cursor information
  557.         
  558.         // display the next cursor
  559.         
  560.         SetCursor( *(recPtr->myCursors[ recPtr->myFrame ]) );
  561.         
  562.         // advance to the next cursor frame
  563.         
  564.         recPtr->myFrame += 1;
  565.         
  566.         // and wrap around to the first frame
  567.         
  568.         if ( recPtr->myFrame > (kNumberOfCursors - 1) )
  569.             recPtr->myFrame = 0;
  570.     }
  571.     
  572.     // and set the task to run again (at every interrupt, the VRMgr decrements this
  573.     // variable (vblCount) by one.  when vblCount hits zero, the task is executed once.
  574.     // to have the task executed again, we have to reset this variable back above zero
  575.     // for the VRMgr to decrement.  incidentally, if you didn't want to VRMgr to execute
  576.     // this task again, you could just set/leave vblCount at zero.  this is one way of
  577.     // disabling a VBL task, so say you had a VBL task that could run during a major/minor
  578.     // context switch (like suspend/resume), you could just set vblCount to 0 upon suspend
  579.     // and it won't execute, then reset it to kInterval upon resume)
  580.     
  581.     recPtr->myVBLTask.vblCount = kInterval;
  582.     
  583.     return;
  584. }
  585.  
  586.  
  587.  
  588.  
  589. // HsoiStartVBLSpinning is our "public" routine to start the whole process.  you call
  590. // this function from "wherever" (remember, there are limits/restrictions as to where you can
  591. // call this from) in your code that you want to start a spinning VBL cursor
  592.  
  593. void    HsoiStartVBLSpinning( void )
  594. {
  595.     OSErr        err;
  596.     short        count;
  597.  
  598.     // get our UPP set up
  599.     if ( gMyVBLUPP == nil )
  600.         gMyVBLUPP = NewVBLProc(HsoiChangeCursor);
  601.         
  602.     // initialize the cursor information
  603.     
  604.     for ( count = 0; count < kNumberOfCursors; count++ )
  605.     {
  606.         // load the cursor into memory
  607.         
  608.         gMyCursTask.myCursors[count] = GetCursor( kInitialResID + count );
  609.         
  610.         // lock the cursor so that we can call SetCursor at interrupt time
  611.         
  612.         HLockHi( (Handle)gMyCursTask.myCursors[count] );
  613.     }
  614.     
  615.     // display cursor with kInitialResID first
  616.     
  617.     gMyCursTask.myFrame = 0;
  618.     
  619.     // initialize the VBL task record
  620.     
  621.     gMyCursTask.myVBLTask.qType = vType;    // set the queue type
  622.     gMyCursTask.myVBLTask.vblAddr = gMyVBLUPP; // get address of VBL task
  623.     gMyCursTask.myVBLTask.vblCount = kInitialDelay; // set task frequency
  624.     // notice by setting the initial delay much larger than the number of interrupts
  625.     // between subsequent cursor changes, we prevent the cursor from starting to spin
  626.     // until a reasonable time (about 2 seconds) has elapsed.  adds a nice, cool
  627.     // cosmetic touch!
  628.     
  629.     gMyCursTask.myVBLTask.vblPhase = 0; // no phase
  630.     
  631.     // make sure our cursor isn't hidden
  632.     
  633.     ShowCursor();
  634.     
  635.     // install it
  636.     
  637.     err = VInstall( (QElemPtr) &(gMyCursTask.myVBLTask) );
  638.     
  639.     return;
  640. }
  641.  
  642. // and this goes along with HsoiStartVBLSpinning.  i'm sure you can guess what it does, but if
  643. // not, HsoiStopVBLSpinning stops the VBL spinning cursor from spinning.  It's probably safe
  644. // to say that any calls to HsoiStartVBLSpinning ought to be soon followed by a HsoiStopVBLSpinning
  645. // call (and there should be equal calls to these in your code, i.e. if you have 3 calls
  646. // to Start, there should be 3 calls to Stop...get why?)
  647.  
  648. void    HsoiStopVBLSpinning( void )
  649. {
  650.     OSErr        err;
  651.     short        count;
  652.     
  653.     // remove the task record from its queue
  654.     
  655.     err = VRemove( (QElemPtr) &(gMyCursTask.myVBLTask) );
  656.     
  657.     // free memory occupied by the cursors
  658.     
  659.     for ( count = 0; count < kNumberOfCursors; count++ )
  660.     {
  661.         HUnlock( (Handle)gMyCursTask.myCursors[count] );
  662.         HsoiForgetResource( (Handle *)&gMyCursTask.myCursors[count] );
  663.     }
  664.     
  665.     // or, if you didn't want to rely on HAS's ForgetResource function, just call this:
  666.     // ReleaseResource( (Handle)gMyCursTask.myCursors[count] );
  667.  
  668.     // dispose our routine descriptor
  669.     
  670.     DisposeRoutineDescriptor( gMyVBLUPP );
  671.     gMyVBLUPP = nil; // make sure the handle is nilled out so we "can't" use it accidentally
  672.  
  673.     // and set the cursor up nicely
  674.     
  675.     InitCursor();
  676.     
  677.     return;
  678. }